#include "abc-article-manager.h"

#include <iomanip>

#include <git2.h>

#include "git-util/git-cert.h"
#include "git-util/git-repo.h"

#include "util/ptime.h"
#include "util/strutil.h"
#include "util/logger/logger.h"

#include "util/config/ui-path.h"
#include "util/config/preference-entry.h"

#include "book/book-convert.h"


static const PreferenceEntry<std::string> prefRepoUri("repo.uri");
static const PreferenceEntry<UiPath> prefRepoLocal("repo.local");

static const PreferenceEntry<UiPath> prefRepoSshPublicKeyPath("repo.ssh.public");
static const PreferenceEntry<UiPath> prefRepoSshPrivateKeyPath("repo.ssh.private");
static const PreferenceEntry<std::string> prefRepoSshPassphrase("repo.ssh.passphrase");

static const PreferenceEntry<std::string> prefRepoHttpsUsername("repo.https.username");
static const PreferenceEntry<std::string> prefRepoHttpsPassword("repo.https.password");

static const PreferenceEntry<std::string> prefArticleSourceDataDir("article.source.dir");
static const PreferenceEntry<UiPath> prefArticleDataDir("article.data.dir");

static const char *title() {
    return "AbcArticleManager ";
}

static git::Cert cert() {
    git::Cert c;
    c.setSshPublicKeyPath(*prefRepoSshPublicKeyPath);
    c.setSshPrivateKeyPath(*prefRepoSshPrivateKeyPath);
    c.setSshPassphrase(*prefRepoSshPassphrase);
    c.setHttpsUsername(*prefRepoHttpsUsername);
    c.setHttpsPassword(*prefRepoHttpsPassword);
    return c;
}

AbcArticleManager::AbcArticleManager() {
    LOGD << title() << "constructed";
}

AbcArticleManager::~AbcArticleManager() {
    LOGD << title() << "destructed";
}

void AbcArticleManager::setOnGenerateSucceedCallback(std::function<void(std::shared_ptr<book::Book>)> &&callback) {
    onGenerateSucceed_ = std::move(callback);
}

void AbcArticleManager::forceGenerate() {
    git::Repo repo(*prefRepoLocal, *prefRepoUri, cert());
    if (repo) {
        repo.pull(cert());
        makeArticleData(repo.head().oid());
    }
}

void AbcArticleManager::afterGitTagPosted() {
    LOGD << title() << "afterGitTagPosted";

    git::Repo repo(*prefRepoLocal, *prefRepoUri, cert());
    if (!repo) {
        return;
    }

    const git::Head oldHead = repo.head();

    repo.pull(cert());

    const git::Head newHead = repo.head();

    if (oldHead != newHead) {
        afterGitRepoContentChanged(newHead.oid());
    }
}

static const char *kBookDataDirBaseName = "abc-book-";
static const char *const kDirNameSuffix = ".abc-data-dir";
static const size_t kDirNameCommitIdLen = 6;

void AbcArticleManager::afterBookDataDirActivated(const fs::path &dir) {
    LOGI << title() << "after book data dir activated: [" << dir << "]";

    if (!dir.has_parent_path()) {
        LOGE << title() << "after book data dir activated, no parent for: [" << dir << "]";
        return;
    }

    const std::string activatedDirName = dir.filename().generic_string();

    const fs::path dataDir = dir.parent_path();

    for (const fs::directory_entry e : fs::directory_iterator(dataDir)) {
        if (!fs::is_directory(e)) {
            continue;
        }
        const std::string foundDirName = e.path().filename().generic_string();
        if (!strutil::startWith(foundDirName, kBookDataDirBaseName)) {
            continue;
        }
        if (!strutil::endWith(foundDirName, kDirNameSuffix)) {
            continue;
        }
        if (foundDirName != activatedDirName) {
            fs::remove_all(e.path());
            LOGD << title() << "old book data dir removed: [" << e.path() << "]";
        }
    }
}

void AbcArticleManager::afterGitRepoContentChanged(const std::string &id) {
    makeArticleData(id);
}

static const size_t kMakeFileNameRetryTimesLimit = 1000;

static std::string makeFileName(const fs::path &dir, const std::string &id) {
    const ptime now = posix_time::second_clock::local_time();
    const auto date = now.date();
    const auto t = now.time_of_day();
    std::ostringstream ss;
    ss << kBookDataDirBaseName << std::setw(4) << std::setfill('0') << date.year();
    ss << '-' << std::setw(2) << std::setfill('0') << date.month().as_number();
    ss << '-' << std::setw(2) << std::setfill('0') << date.day().as_number();
    ss << '_' << std::setw(2) << std::setfill('0') << t.hours();
    ss << '-' << std::setw(2) << std::setfill('0') << t.minutes();
    ss << '-' << std::setw(2) << std::setfill('0') << t.seconds();
    ss << '-' << id.substr(0, kDirNameCommitIdLen);

    const std::string n = ss.str();

    if (!fs::exists(dir / (n + kDirNameSuffix))) {
        return n + kDirNameSuffix;
    }

    for (size_t i = 0; i < kMakeFileNameRetryTimesLimit; ++i) {
        const std::string tmp = n + "-" + strutil::convert(i) + kDirNameSuffix;
        if (!fs::exists(dir / tmp)) {
            return tmp;
        }
    }

    throw AbcArticleManager::ChooseBookDirError();
}

void AbcArticleManager::makeArticleData(const std::string &id) {

    const fs::path dataDir = prefArticleDataDir->str();

    fs::create_directories(dataDir);

    const fs::path bookDataDir = fs::path(dataDir / makeFileName(dataDir, id));
    if (fs::exists(bookDataDir)) {
        fs::remove_all(bookDataDir);
    }
    fs::create_directories(bookDataDir);

    const fs::path srcdir = fs::path(prefRepoLocal->str()) / (*prefArticleSourceDataDir);
    book::BookConvert convert(srcdir, bookDataDir);
    std::shared_ptr<book::Book> book = std::shared_ptr<book::Book>(new book::Book(convert()));

    if (onGenerateSucceed_) {
        onGenerateSucceed_(book);
    } else {
        LOGW << title() << "onGeneratedSucceed callback not set";
    }
}
